﻿//------------------------------------------------------------------------------
// <copyright company="Tunynet">
//     Copyright (c) Tunynet Inc.  All rights reserved.
// </copyright> 
//------------------------------------------------------------------------------

using Lucene.Net.Analysis;
using Lucene.Net.Analysis.PanGu;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Search;
using Lucene.Net.Store;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Tunynet.Utilities;

namespace Tunynet.Search
{
    /// <summary>
    /// 基于Lucene NRT技术的搜索引擎，包括索引创建及搜索实现
    /// </summary>
    public class SearchEngine : ISearchEngine
    {
        private Analyzer analyzer;
        private int mergeFactor;
        private string indexPath;
        private bool initialized = false;
        private static PanGu.HighLight.Highlighter highlighter = null;
        private object _lock = new object();
        private static readonly object lockObject = new object();

        //以indexPath为key，存储IndexWriter
        private static ConcurrentDictionary<string, IndexWriter> indexWriters = new ConcurrentDictionary<string, IndexWriter>();

        //以indexPath为key，存储SearcherManager
        private static ConcurrentDictionary<string, SearcherManager> searcherManagers = new ConcurrentDictionary<string, SearcherManager>();

        /// <summary>
        /// 无参数构造函数
        /// 其存在是为了满足WCF的要求，在此情况下，因为indexPath对于SearchEngine是必须的，所以WCF的客户端还需要调用一下Initialize方法
        /// 如果是本地调用了有参数的构造函数，则无需调用Initialize方法
        /// </summary>
        protected SearchEngine()
        {
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="indexPath">索引路径</param>
        public SearchEngine(string indexPath)
        {
            Initialize(indexPath);
        }

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="indexPath">索引路径</param>
        /// <param name="mergeFactor">索引合并参数，决定了在 Lucene 的一个索引块中可以存放多少文档以及把磁盘上的索引块合并成一个大的索引块的频率。比如，如果合并因子的值是 10，那么当内存中的文档数达到 10 的时候所有的文档都必须写到磁盘上的一个新的索引块中。并且，如果磁盘上的索引块的隔数达到 10 的话，这 10 个索引块会被合并成一个新的索引块。这个参数的默认值是 10，如果需要索引的文档数非常多的话这个值将是非常不合适的。对批处理的索引来讲，为这个参数赋一个比较大的值会得到比较好的索引效果</param>
        /// <param name="analyzer">分词器，默认盘古分词</param>
        public SearchEngine(string indexPath, int mergeFactor, Analyzer analyzer)
        {
            Initialize(indexPath, mergeFactor, analyzer);
        }

        /// <summary>
        /// 初始化搜索引擎
        /// </summary>
        /// <param name="indexPath">索引路径</param>
        /// <param name="mergeFactor">索引合并参数，决定了在 Lucene 的一个索引块中可以存放多少文档以及把磁盘上的索引块合并成一个大的索引块的频率。比如，如果合并因子的值是 10，那么当内存中的文档数达到 10 的时候所有的文档都必须写到磁盘上的一个新的索引块中。并且，如果磁盘上的索引块的隔数达到 10 的话，这 10 个索引块会被合并成一个新的索引块。这个参数的默认值是 10，如果需要索引的文档数非常多的话这个值将是非常不合适的。对批处理的索引来讲，为这个参数赋一个比较大的值会得到比较好的索引效果</param>
        /// <param name="analyzer">分词器，默认盘古分词</param>
        public void Initialize(string indexPath, int mergeFactor = 10, Analyzer analyzer = null)
        {
            //只能初始化一次，防止客户端重复调用
            if (!initialized)
            {
                lock (lockObject)
                {
                    if (!initialized)
                    {
                        this.indexPath = WebUtility.GetPhysicalFilePath(indexPath);

                        if (mergeFactor < 10)
                        {
                            mergeFactor = 10;
                        }
                        this.mergeFactor = mergeFactor;

                        //使用盘古分词
                        if (analyzer == null)
                        {
                            analyzer = new PanGuAnalyzer();
                        }
                        this.analyzer = analyzer;

                        //初始化SearcherManager
                        InitSearcherManager();

                        initialized = true;
                    }
                }
            }
        }

        /// <summary>
        /// 获取当前搜索引擎的索引大小
        /// </summary>
        public double GetIndexSize()
        {
            double indexSize = 0;
            if (IsIndexFileExists())
            {
                FileInfo[] indexFiles = (new DirectoryInfo(this.indexPath)).GetFiles("*.*");
                if (indexFiles != null && indexFiles.Length > 0)
                {
                    foreach (FileInfo file in indexFiles)
                    {
                        indexSize += file.Length;
                    }
                }
            }

            return indexSize;
        }

        /// <summary>
        /// 获取友好格式索引大小
        /// </summary>
        /// <returns></returns>
        public string GetFriendlyIndexSize()
        {
            var fileLength = GetIndexSize();

            if (fileLength > 0)
            {
                if (fileLength > 1024 * 1024)
                    return string.Format("{0:F2}M", (fileLength / (1024 * 1024F)));
                else
                    return string.Format("{0:F2}K", (fileLength / 1024F));
            }
            else
                return string.Empty;
        }

        /// <summary>
        /// 获取当前搜索引擎的索引最后更新时间
        /// </summary>
        public DateTime GetLastModified()
        {
            DateTime lastModified = DateTime.MinValue;
            if (IsIndexFileExists())
            {
                FileInfo[] indexFiles = (new DirectoryInfo(this.indexPath)).GetFiles("*.*");
                if (indexFiles != null && indexFiles.Length > 0)
                {
                    foreach (FileInfo file in indexFiles)
                    {
                        if (file.LastWriteTime > lastModified)
                        {
                            lastModified = file.LastWriteTime;
                        }
                    }
                }
            }

            return lastModified;
        }

        /// <summary>
        /// 重建索引
        /// </summary>
        /// <remarks>
        /// 重建索引数据量大，一般分多次进行调用本方法，用isBeginning和isEndding参数；
        /// 重建索引会重置搜索管理器，搜索服务可能在切换索引时短时中断；
        /// 重建索引的目的是为了避免因意外情况导致索引不完整
        /// </remarks>
        /// <param name="indexDocuments">待添加的内容</param>
        /// <param name="isBeginning">开始重建</param>
        /// <param name="isEndding">完成重建</param>
        public void RebuildIndex(IEnumerable<Document> indexDocuments, bool isBeginning, bool isEndding)
        {
            lock (_lock)
            {
                string reIndexPath = Path.Combine(indexPath, "ReIndex");
                IndexWriter indexWriter = null;

                if (isBeginning)
                {
                    if (System.IO.Directory.Exists(reIndexPath))
                    {
                        //删除索引文件
                        string[] indexFiles = System.IO.Directory.GetFiles(reIndexPath);
                        foreach (string file in indexFiles)
                        {
                            File.Delete(file);
                        }
                    }
                    else
                    {
                        //创建索引目录
                        System.IO.Directory.CreateDirectory(reIndexPath);
                    }

                    //创建索引文件
                    Lucene.Net.Store.Directory indexDirectory = FSDirectory.Open(new DirectoryInfo(reIndexPath));
                    indexWriter = new IndexWriter(indexDirectory, analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED);
                    indexWriters[reIndexPath] = indexWriter;
                }
                else
                {
                    indexWriter = GetIndexWriter(reIndexPath);
                }

                //向索引里增加文档
                if (indexDocuments != null && indexDocuments.Count<Document>() > 0)
                {
                    try
                    {
                        foreach (var indexDocument in indexDocuments)
                        {
                            indexWriter.AddDocument(indexDocument);
                        }
                    }
                    catch (Exception ex)
                    {
                        throw new ExceptionFacade(string.Format("An unexpected error occured while add documents to the index [{0}].", this.indexPath), ex);
                    }
                }

                if (isEndding)
                {
                    //优化合并索引
                    indexWriter.Optimize();

                    //关闭重建的索引
                    CloseIndexWriter(reIndexPath);

                    //关闭搜索管理器
                    CloseSearcherManager();

                    //关闭旧索引
                    CloseIndexWriter();

                    //删除旧的索引文件
                    string[] indexFiles = System.IO.Directory.GetFiles(this.indexPath, "*.*", SearchOption.TopDirectoryOnly);
                    foreach (string file in indexFiles)
                    {
                        File.Delete(file);
                    }

                    //移动重建的索引至索引目录
                    indexFiles = System.IO.Directory.GetFiles(reIndexPath, "*.*", SearchOption.TopDirectoryOnly);
                    foreach (var file in indexFiles)
                    {
                        System.IO.File.Move(file, file.Replace(@"\ReIndex", string.Empty));
                    }

                    //删除重建的索引目录
                    System.IO.Directory.Delete(reIndexPath);

                    //重新初始化搜索管理器
                    InitSearcherManager();
                }
            }
        }

        /// <summary>
        /// 添加索引内容
        /// </summary>
        /// <param name="indexDocument">待添加的索引文档</param>
        public void Insert(Document indexDocument, bool reopen = true)
        {
            Insert(new Document[] { indexDocument }, reopen);
        }

        /// <summary>
        /// 添加索引内容
        /// </summary>
        /// <param name="indexDocuments">待添加的索引文档</param>
        /// <param name="reopen">是否重新打开索引</param>
        public void Insert(IEnumerable<Document> indexDocuments, bool reopen = true)
        {
            lock (_lock)
            {
                if (indexDocuments == null || indexDocuments.Count() == 0)
                {
                    return;
                }
                IndexWriter indexWriter = GetIndexWriter();

                try
                {
                    foreach (var indexDocument in indexDocuments)
                    {
                        indexWriter.AddDocument(indexDocument);
                    }
                }
                catch (Exception ex)
                {
                    throw new ExceptionFacade(string.Format("An unexpected error occured while add documents to the index [{0}].", this.indexPath), ex);
                }

                if (reopen)
                {
                    ReopenSearcher();
                }
            }
        }

        /// <summary>
        /// 删除索引内容
        /// </summary>
        /// <param name="id">索引内容对应的实体主键</param>
        /// <param name="fieldNameOfId">实体主键对应的索引字段名称</param>
        /// <param name="reopen">是否重新打开NRT查询</param>
        public void Delete(string id, string fieldNameOfId, bool reopen = true)
        {
            Delete(new string[] { id }, fieldNameOfId, reopen);
        }

        /// <summary>
        /// 删除索引内容
        /// </summary>
        /// <param name="ids">索引内容对应的实体主键</param>
        /// <param name="fieldNameOfId">实体主键对应的索引字段名称</param>
        /// <param name="reopen">是否重新打开NRT查询</param>
        public void Delete(IEnumerable<string> ids, string fieldNameOfId, bool reopen = true)
        {
            lock (_lock)
            {
                if (ids == null && ids.Count() == 0)
                {
                    return;
                }

                IndexWriter indexWriter = GetIndexWriter();
                try
                {
                    List<Term> terms = new List<Term>();
                    foreach (var id in ids)
                    {
                        Term term = new Term(fieldNameOfId, id);
                        terms.Add(term);
                    }

                    indexWriter.DeleteDocuments(terms.ToArray());
                }
                catch (Exception ex)
                {
                    throw new ExceptionFacade(string.Format("An unexpected error occured while delete documents to the index [{0}].", this.indexPath), ex);
                }

                if (reopen)
                {
                    ReopenSearcher();
                }
            }
        }

        /// <summary>
        /// 更新索引内容
        /// </summary>
        /// <param name="indexDocument">索引文档</param>
        /// <param name="id">实体主键</param>
        /// <param name="fieldNameOfId">实体主键对应的索引字段名称</param>
        /// <param name="reopen">是否重新打开NRT查询</param>
        public void Update(Document indexDocument, string id, string fieldNameOfId, bool reopen = true)
        {
            Update(new Document[] { indexDocument }, new string[] { id }, fieldNameOfId, reopen);
        }

        /// <summary>
        /// 更新索引内容
        /// </summary>
        /// <param name="indexDocuments">索引文档</param>
        /// <param name="ids">实体主键</param>
        /// <param name="fieldNameOfId">实体主键对应的索引字段名称</param>
        /// <param name="reopen">是否重新打开NRT查询</param>
        public void Update(IEnumerable<Document> indexDocuments, IEnumerable<string> ids, string fieldNameOfId, bool reopen = true)
        {
            lock (_lock)
            {
                if (indexDocuments == null || indexDocuments.Count() == 0 || ids == null || ids.Count() == 0)
                {
                    return;
                }

                //先删除索引文档
                try
                {
                    Delete(ids, fieldNameOfId, false);
                }
                catch (Exception ex)
                {
                    throw new ExceptionFacade(string.Format("An unexpected error occured while delete documents to the index [{0}].", this.indexPath), ex);
                }

                //再添加新的索引文档
                try
                {
                    Insert(indexDocuments, false);
                }
                catch (Exception ex)
                {
                    throw new ExceptionFacade(string.Format("An unexpected error occured while add documents to the index [{0}].", this.indexPath), ex);
                }

                if (reopen)
                {
                    ReopenSearcher();
                }
            }
        }

        /// <summary>
        /// 提交索引变更
        /// 由于使用了Lucene的近实时搜索（NRT），IndexWriter一直保持打开状态，add、update、delete等操作对索引的变更会近实时反映到搜索结果，
        /// 但是在执行IndexWriter的Commit方法钱，索引的变更内容不会记录到磁盘，因此需要使用定时任务周期性调用本方法完成索引的持久化
        /// </summary>
        public void Commit()
        {
            lock (_lock)
            {
                IndexWriter indexWriter = GetIndexWriter();
                indexWriter.Commit();
            }
        }

        /// <summary>
        /// 关闭索引
        /// </summary>
        public void Close()
        {
            lock (_lock)
            {
                CloseIndexWriter();
            }
        }

        /// <summary>
        /// 优化索引，由定时任务执行
        /// </summary>
        public void Optimize()
        {
            lock (_lock)
            {
                IndexWriter indexWriter = GetIndexWriter();
                indexWriter.Optimize();
            }
        }

        /// <summary>
        /// 搜索并返回分页数据
        /// </summary>
        /// <param name="searchQuery"><see cref="Lucene.Net.Search.Query"/></param>
        /// <param name="filter"><see cref="Lucene.Net.Search.Filter"/></param>
        /// <param name="sort"><see cref="Lucene.Net.Search.Sort"/></param>
        /// <param name="pageIndex">当前页码</param>
        /// <param name="pageSize">每页记录数</param>
        /// <returns>内容为<see cref="Lucene.Net.Documents.Document"/>的分页数据集合</returns>
        public PagingDataSet<Document> Search(Query searchQuery, Filter filter, Sort sort, int pageIndex, int pageSize)
        {
            //索引文件不存在时，返回空集合
            if (!IsIndexFileExists(this.indexPath))
            {
                return new PagingDataSet<Document>(Enumerable.Empty<Document>());
            }

            //搜索事件监听器
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            if (sort == null)
            {
                sort = Sort.RELEVANCE;
            }

            TopFieldCollector topCollector = TopFieldCollector.Create(sort, pageIndex * pageSize, false, true, false, true);

            //获取搜索管理器
            SearcherManager searcherManager = GetSearcherManager();

            //从搜索管理器得到搜索器
            IndexSearcher searcher = searcherManager.GetSearcher();

            try
            {
                //搜索
                searcher.Search(searchQuery, filter, topCollector);
                IEnumerable<Document> hits = topCollector.TopDocs().ScoreDocs.Skip((pageIndex - 1) * pageSize).Select(scoreDoc => searcher.Doc(scoreDoc.Doc));

                stopwatch.Stop();

                //构建分页对象
                PagingDataSet<Document> pds = new PagingDataSet<Document>(hits)
                {
                    TotalRecords = topCollector.TotalHits,
                    PageSize = pageSize,
                    PageIndex = pageIndex,
                    QueryDuration = (double)stopwatch.ElapsedMilliseconds / 1000d
                };

                return pds;
            }
            catch (Exception ex)
            {
                throw new ExceptionFacade(string.Format("Index file maybe not exist under path: [{0}].", this.indexPath), ex);
            }
            finally
            {
                //释放搜索管理器资源
                searcherManager.ReleaseSearcher(searcher);
            }
        }

        /// <summary>
        /// 搜索并返回数据
        /// </summary>
        /// <param name="searchQuery"><see cref="Lucene.Net.Search.Query"/></param>
        /// <param name="filter"><see cref="Lucene.Net.Search.Filter"/></param>
        /// <param name="sort"><see cref="Lucene.Net.Search.Sort"/></param>
        /// <returns></returns>
        public IEnumerable<Document> Search(Query searchQuery, Filter filter, Sort sort)
        {
            //索引文件不存在时，返回空集合
            if (!IsIndexFileExists(this.indexPath))
            {
                return new PagingDataSet<Document>(Enumerable.Empty<Document>());
            }

            //搜索事件监听器
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            if (sort == null)
            {
                sort = Sort.RELEVANCE;
            }

            TopFieldCollector topCollector = TopFieldCollector.Create(sort, 99999, false, true, false, true);

            //获取搜索管理器
            SearcherManager searcherManager = GetSearcherManager();

            //从搜索管理器得到搜索器
            IndexSearcher searcher = searcherManager.GetSearcher();

            try
            {
                //搜索
                searcher.Search(searchQuery, filter, topCollector);
                IEnumerable<Document> hits = topCollector.TopDocs().ScoreDocs.Select(scoreDoc => searcher.Doc(scoreDoc.Doc));

                stopwatch.Stop();

                return hits;
            }
            catch (Exception ex)
            {
                throw new ExceptionFacade(string.Format("Index file maybe not exist under path: [{0}].", this.indexPath), ex);
            }
            finally
            {
                //释放搜索管理器资源
                searcherManager.ReleaseSearcher(searcher);
            }
        }

        /// <summary>
        /// 搜索并返回前topNumber条数据
        /// </summary>
        /// <param name="searchQuery"><see cref="Lucene.Net.Search.Query"/></param>
        /// <param name="filter"><see cref="Lucene.Net.Search.Filter"/></param>
        /// <param name="sort"><see cref="Lucene.Net.Search.Sort"/></param>
        /// <param name="topNumber">最多返回多少条数据</param>
        /// <returns></returns>
        public IEnumerable<Document> Search(Query searchQuery, Filter filter, Sort sort, int topNumber)
        {
            //搜索事件监听器
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();

            if (sort == null)
            {
                sort = Sort.RELEVANCE;
            }

            //获取搜索管理器
            SearcherManager searcherManager = GetSearcherManager();

            //从搜索管理器得到搜索器
            IndexSearcher searcher = searcherManager.GetSearcher();

            try
            {
                //搜索
                TopFieldDocs collector = searcher.Search(searchQuery, filter, topNumber, sort);
                IEnumerable<Document> hits = collector.ScoreDocs.Select(scoreDoc => searcher.Doc(scoreDoc.Doc));

                return hits;
            }
            catch (Exception ex)
            {
                throw new ExceptionFacade(string.Format("Index file maybe not exist under path: [{0}].", this.indexPath), ex);
            }
            finally
            {
                //释放搜索管理器资源
                searcherManager.ReleaseSearcher(searcher);
            }
        }

        /// <summary>
        /// 以搜索词对内容进行高亮
        /// </summary>
        /// <param name="keyword">搜索词</param>
        /// <param name="content">待处理的内容</param>
        /// <param name="maxLengthOfResult">处理后的字符串最大长度（超出后截断）</param>
        /// <returns>返回高亮后的字符串</returns>
        public static string Highlight(string keyword, string content, int maxLengthOfResult)
        {
            if (highlighter == null)
            {
                highlighter = new PanGu.HighLight.Highlighter(new PanGu.HighLight.SimpleHTMLFormatter("<span class='tn-theme-color'>", "</span>"), new PanGu.Segment());
                highlighter.FragmentSize = maxLengthOfResult;
            }

            //content太短可能导致 highlighter.GetBestFragment(keyWord, content)为空字符串
            int minLengthForHighlight = 1;

            string highlightedContent = null;
            if (!string.IsNullOrEmpty(content) && content.Length > minLengthForHighlight)
                highlightedContent = highlighter.GetBestFragment(keyword, content);

            return string.IsNullOrEmpty(highlightedContent) ? StringUtility.Trim(content, maxLengthOfResult) : highlightedContent;
        }

        /// <summary>
        /// 初始化搜索管理器<see cref="SearcherManager"/>
        /// </summary>
        private void InitSearcherManager()
        {
            //防止重复打开IndexWriter
            IndexWriter indexWriter = GetIndexWriter();
            if (indexWriter != null)
            {
                return;
            }

            //先检查索引目录是否存在，如不存在，则创建缓存
            var directory = new DirectoryInfo(this.indexPath);
            if (!directory.Exists)
            {
                directory.Create();
            }

            Lucene.Net.Store.Directory indexDirectory = FSDirectory.Open(new DirectoryInfo(this.indexPath));
            //检查索引是否存在
            if (IndexReader.IndexExists(Lucene.Net.Store.FSDirectory.Open(directory)))
            {
                // 第一个参数是存放索引目录有FSDirectory（存储到磁盘上）和RAMDirectory（存储到内存中）， 第二个参数是使用的分词器， 第三个：true，建立全新的索引，false,建立增量索引，第四个是.字段的最长文本，如果超过这个最长的文本，就切除超过的部分。
                indexWriter = new IndexWriter(indexDirectory, analyzer, false, IndexWriter.MaxFieldLength.UNLIMITED);
            }
            else
            {
                indexWriter = new IndexWriter(indexDirectory, analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED);
            }

            // 设置索引合并参数
            //indexWriter.SetMergeFactor(this.mergeFactor);
            indexWriter.MergeFactor = this.mergeFactor;

            //将IndexWriter加入缓存
            indexWriters[this.indexPath] = indexWriter;

            //从IndexWriter中构建SearcherManager，并放入缓存
            SearcherManager searcherManager = new SearcherManager(indexWriter);
            searcherManagers[this.indexPath] = searcherManager;

            initialized = true;
        }

        /// <summary>
        /// 获取当前索引路径下的搜索管理器
        /// </summary>
        /// <returns><see cref="SearcherManager"/></returns>
        private SearcherManager GetSearcherManager()
        {
            return GetSearcherManager(this.indexPath);
        }

        /// <summary>
        /// 获取指定索引路径下的搜索管理器
        /// </summary>
        /// <param name="indexPath"></param>
        /// <returns>索引目录</returns>
        private SearcherManager GetSearcherManager(string indexPath)
        {
            if (searcherManagers.ContainsKey(indexPath))
            {
                return searcherManagers[indexPath];
            }

            return null;
        }

        /// <summary>
        /// 关闭当前索引路径下的搜索管理器
        /// </summary>
        private void CloseSearcherManager()
        {
            CloseSearcherManager(this.indexPath);
        }

        /// <summary>
        /// 关闭指定索引路径下的搜索管理器
        /// </summary>
        /// <param name="indexPath">索引目录</param>
        private void CloseSearcherManager(string indexPath)
        {
            if (searcherManagers.ContainsKey(indexPath))
            {
                SearcherManager searcherManager = null;

                searcherManagers.TryRemove(indexPath, out searcherManager);
                if (searcherManager != null)
                {
                    searcherManager.Close();
                }
            }
        }

        /// <summary>
        /// 获得当前索引路径下的IndexWriter，对于相同的索引路径，只存在一个IndexWriter
        /// </summary>
        /// <returns>IndexWriter</returns>
        private IndexWriter GetIndexWriter()
        {
            return GetIndexWriter(this.indexPath);
        }

        /// <summary>
        /// 获取指定索引路径下的IndexWriter，对于相同的索引路径，只存在一个IndexWriter
        /// </summary>
        /// <param name="indexPath">索引路径</param>
        /// <returns>IndexWriter</returns>
        private IndexWriter GetIndexWriter(string indexPath)
        {
            if (indexWriters.ContainsKey(indexPath))
            {
                return indexWriters[indexPath];
            }

            return null;
        }

        /// <summary>
        /// 关闭当前索引路径下的IndexWriter
        /// </summary>
        private void CloseIndexWriter()
        {
            CloseIndexWriter(this.indexPath);
        }

        /// <summary>
        ///  关闭指定索引路径下的IndexWriter
        /// </summary>
        /// <param name="indexPath"></param>
        private void CloseIndexWriter(string indexPath)
        {
            if (indexWriters.ContainsKey(indexPath))
            {
                IndexWriter indexWriter = null;

                indexWriters.TryRemove(indexPath, out indexWriter);
                if (indexWriter != null)
                {
                    //indexWriter.Close();
                    indexWriter.Dispose();
                    indexWriter = null;
                }
            }
        }

        /// <summary>
        /// 重新打开搜索器，用于近实时搜索（NRT）
        /// </summary>
        private void ReopenSearcher()
        {
            SearcherManager searcherManager = GetSearcherManager();
            searcherManager.Reopen();
        }

        /// <summary>
        /// 检查当前索引路径下的索引文件是否存在
        /// </summary>
        private bool IsIndexFileExists()
        {
            return IsIndexFileExists(this.indexPath);
        }

        /// <summary>
        /// 检查指定索引路径下的索引文件是否存在
        /// </summary>
        /// <param name="indexPath">索引路径</param>
        /// <returns></returns>
        private bool IsIndexFileExists(string indexPath)
        {
            return IndexReader.IndexExists(Lucene.Net.Store.FSDirectory.Open(new System.IO.DirectoryInfo(indexPath)));
        }
    }
}